import 'dart:io';

import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_mlkit_commons/google_mlkit_commons.dart';

class CameraView extends StatefulWidget {
  CameraView(
      {Key key,
      @required this.customPaint,
      @required this.onImage,
      this.onCameraFeedReady,
      this.onDetectorViewModeChanged,
      this.onCameraLensDirectionChanged,
      this.initialCameraLensDirection = CameraLensDirection.back,
      @required this.onTakePicture})
      : super(key: key);

  final CustomPaint customPaint;
  final Function(InputImage inputImage, CameraController controller) onImage;
  final Function(XFile xfile) onTakePicture;
  final VoidCallback onCameraFeedReady;
  final VoidCallback onDetectorViewModeChanged;
  final Function(CameraLensDirection direction) onCameraLensDirectionChanged;
  final CameraLensDirection initialCameraLensDirection;

  @override
  State<CameraView> createState() => _CameraViewState();
}

class _CameraViewState extends State<CameraView> {
  static List<CameraDescription> _cameras = [];
  CameraController _controller;
  int _cameraIndex = -1;
  double _currentZoomLevel = 1.0;
  double _minAvailableZoom = 1.0;
  double _maxAvailableZoom = 1.0;
  double _minAvailableExposureOffset = 0.0;
  double _maxAvailableExposureOffset = 0.0;
  double _currentExposureOffset = 0.0;
  bool _changingCameraLens = false;

  @override
  void initState() {
    super.initState();

    _initialize();
  }

  void _initialize() async {
    if (_cameras.isEmpty) {
      _cameras = await availableCameras();
    }
    for (var i = 0; i < _cameras.length; i++) {
      if (_cameras[i].lensDirection == widget.initialCameraLensDirection) {
        _cameraIndex = i;
        break;
      }
    }
    if (_cameraIndex != -1) {
      _startLiveFeed();
    }
  }

  @override
  void dispose() {
    _stopLiveFeed();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(body: _liveFeedBody());
  }

  Widget _liveFeedBody() {
    if (_cameras.isEmpty) return Container();
    if (_controller == null) return Container();
    if (_controller?.value?.isInitialized == false) return Container();
    return ColoredBox(
      color: Colors.black,
      child: Stack(
        fit: StackFit.expand,
        children: <Widget>[
          Center(
            child: _changingCameraLens
                ? Center(
                    child: const Text('相机修改失败请退出重试'),
                  )
                : CameraPreview(
                    _controller,
                    child: widget.customPaint,
                  ),
          ),
          // _backButton(),
          Positioned.fill(
            child: QRCodeEffectPage(),
          ),
          // _switchLiveCameraToggle(),
          // _detectionViewModeToggle(),
          // _zoomControl(),
          // _exposureControl(),
          // Positioned(
          //     right: 20,
          //     bottom: 20,
          //     child: ElevatedButton(
          //       onPressed: () async {
          //         XFile xfild = await _controller.takePicture();
          //         debugPrint("生成图片 ${xfild.path}");
          //         widget.onTakePicture(xfild);
          //       },
          //       child: Text("保存图片"),
          //     )),
        ],
      ),
    );
  }

  Widget _detectionViewModeToggle() => Positioned(
        bottom: 8,
        left: 8,
        child: SizedBox(
          height: 50.0,
          width: 50.0,
          child: FloatingActionButton(
            heroTag: Object(),
            onPressed: widget.onDetectorViewModeChanged,
            backgroundColor: Colors.black54,
            child: Icon(
              Icons.photo_library_outlined,
              size: 25,
            ),
          ),
        ),
      );

  Widget _switchLiveCameraToggle() => Positioned(
        bottom: 8,
        right: 8,
        child: SizedBox(
          height: 50.0,
          width: 50.0,
          child: FloatingActionButton(
            heroTag: Object(),
            onPressed: _switchLiveCamera,
            backgroundColor: Colors.black54,
            child: Icon(
              Platform.isIOS
                  ? Icons.flip_camera_ios_outlined
                  : Icons.flip_camera_android_outlined,
              size: 25,
            ),
          ),
        ),
      );

  Widget _zoomControl() => Positioned(
        bottom: 16,
        left: 0,
        right: 0,
        child: Align(
          alignment: Alignment.bottomCenter,
          child: SizedBox(
            width: 250,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                Expanded(
                  child: Slider(
                    value: _currentZoomLevel,
                    min: _minAvailableZoom,
                    max: _maxAvailableZoom,
                    activeColor: Colors.white,
                    inactiveColor: Colors.white30,
                    onChanged: (value) async {
                      setState(() {
                        _currentZoomLevel = value;
                      });
                      await _controller?.setZoomLevel(value);
                    },
                  ),
                ),
                Container(
                  width: 50,
                  decoration: BoxDecoration(
                    color: Colors.black54,
                    borderRadius: BorderRadius.circular(10.0),
                  ),
                  child: Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: Center(
                      child: Text(
                        '${_currentZoomLevel.toStringAsFixed(1)}x',
                        style: TextStyle(color: Colors.white),
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ),
      );

  Widget _exposureControl() => Positioned(
        top: 40,
        right: 8,
        child: ConstrainedBox(
          constraints: BoxConstraints(
            maxHeight: 250,
          ),
          child: Column(children: [
            Container(
              width: 55,
              decoration: BoxDecoration(
                color: Colors.black54,
                borderRadius: BorderRadius.circular(10.0),
              ),
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Center(
                  child: Text(
                    '${_currentExposureOffset.toStringAsFixed(1)}x',
                    style: TextStyle(color: Colors.white),
                  ),
                ),
              ),
            ),
            Expanded(
              child: RotatedBox(
                quarterTurns: 3,
                child: SizedBox(
                  height: 30,
                  child: Slider(
                    value: _currentExposureOffset,
                    min: _minAvailableExposureOffset,
                    max: _maxAvailableExposureOffset,
                    activeColor: Colors.white,
                    inactiveColor: Colors.white30,
                    onChanged: (value) async {
                      setState(() {
                        _currentExposureOffset = value;
                      });
                      await _controller?.setExposureOffset(value);
                    },
                  ),
                ),
              ),
            )
          ]),
        ),
      );

  Future _startLiveFeed() async {
    final camera = _cameras[_cameraIndex];
    _controller = CameraController(
      camera,
      // Set to ResolutionPreset.high. Do NOT set it to ResolutionPreset.max because for some phones does NOT work.
      ResolutionPreset.high,
      enableAudio: false,
      imageFormatGroup: Platform.isAndroid
          ? ImageFormatGroup.nv21
          : ImageFormatGroup.bgra8888,
    );
    _controller?.initialize().then((_) {
      if (!mounted) {
        return;
      }
      _controller?.getMinZoomLevel().then((value) {
        _currentZoomLevel = value;
        _minAvailableZoom = value;
      });
      _controller?.getMaxZoomLevel().then((value) {
        _maxAvailableZoom = value;
      });
      _currentExposureOffset = 0.0;
      _controller?.getMinExposureOffset().then((value) {
        _minAvailableExposureOffset = value;
      });
      _controller?.getMaxExposureOffset().then((value) {
        _maxAvailableExposureOffset = value;
      });
      // 开始接收图像流
      _controller?.startImageStream(_processCameraImage).then((value) {
        if (widget.onCameraFeedReady != null) {
          widget.onCameraFeedReady();
        }
        if (widget.onCameraLensDirectionChanged != null) {
          widget.onCameraLensDirectionChanged(camera.lensDirection);
        }
      });
      setState(() {});
    });
  }

  Future _stopLiveFeed() async {
    await _controller?.stopImageStream();
    await _controller?.dispose();
    _controller = null;
  }

  Future _switchLiveCamera() async {
    setState(() => _changingCameraLens = true);
    _cameraIndex = (_cameraIndex + 1) % _cameras.length;

    await _stopLiveFeed();
    await _startLiveFeed();
    setState(() => _changingCameraLens = false);
  }

  int index = 0;
  void _processCameraImage(CameraImage image) {
    index++;
    if (index % 20 == 0) {
      final inputImage = _inputImageFromCameraImage(image);
      if (inputImage == null) return;
      debugPrint("相机帧率回调控制 $index --执行回调");
      widget.onImage(inputImage, _controller);
    } else {
      debugPrint("相机帧率回调控制 $index");
    }
  }

  final _orientations = {
    DeviceOrientation.portraitUp: 0,
    DeviceOrientation.landscapeLeft: 90,
    DeviceOrientation.portraitDown: 180,
    DeviceOrientation.landscapeRight: 270,
  };

  InputImage _inputImageFromCameraImage(CameraImage image) {
    if (_controller == null) return null;

    // get image rotation
    // it is used in android to convert the InputImage from Dart to Java: https://github.com/flutter-ml/google_ml_kit_flutter/blob/master/packages/google_mlkit_commons/android/src/main/java/com/google_mlkit_commons/InputImageConverter.java
    // `rotation` is not used in iOS to convert the InputImage from Dart to Obj-C: https://github.com/flutter-ml/google_ml_kit_flutter/blob/master/packages/google_mlkit_commons/ios/Classes/MLKVisionImage%2BFlutterPlugin.m
    // in both platforms `rotation` and `camera.lensDirection` can be used to compensate `x` and `y` coordinates on a canvas: https://github.com/flutter-ml/google_ml_kit_flutter/blob/master/packages/example/lib/vision_detector_views/painters/coordinates_translator.dart
    final camera = _cameras[_cameraIndex];
    final sensorOrientation = camera.sensorOrientation;
    // print(
    //     'lensDirection: ${camera.lensDirection}, sensorOrientation: $sensorOrientation, ${_controller?.value.deviceOrientation} ${_controller?.value.lockedCaptureOrientation} ${_controller?.value.isCaptureOrientationLocked}');
    InputImageRotation rotation;
    if (Platform.isIOS) {
      rotation = InputImageRotationValue.fromRawValue(sensorOrientation);
    } else if (Platform.isAndroid) {
      var rotationCompensation =
          _orientations[_controller.value.deviceOrientation];
      if (rotationCompensation == null) return null;
      if (camera.lensDirection == CameraLensDirection.front) {
        // front-facing
        rotationCompensation = (sensorOrientation + rotationCompensation) % 360;
      } else {
        // back-facing
        rotationCompensation =
            (sensorOrientation - rotationCompensation + 360) % 360;
      }
      rotation = InputImageRotationValue.fromRawValue(rotationCompensation);
      // print('rotationCompensation: $rotationCompensation');
    }
    if (rotation == null) return null;
    // print('final rotation: $rotation');

    // get image format
    final format = InputImageFormatValue.fromRawValue(image.format.raw);
    // validate format depending on platform
    // only supported formats:
    // * nv21 for Android
    // * bgra8888 for iOS
    if (format == null ||
        (Platform.isAndroid && format != InputImageFormat.nv21) ||
        (Platform.isIOS && format != InputImageFormat.bgra8888)) return null;

    // since format is constraint to nv21 or bgra8888, both only have one plane
    if (image.planes.length != 1) return null;
    final plane = image.planes.first;

    // compose InputImage using bytes
    return InputImage.fromBytes(
      bytes: plane.bytes,
      metadata: InputImageMetadata(
        size: Size(image.width.toDouble(), image.height.toDouble()),
        rotation: rotation, // used only in Android
        format: format, // used only in iOS
        bytesPerRow: plane.bytesPerRow, // used only in iOS
      ),
    );
  }
}

class QRCodeEffectPage extends StatelessWidget {
  const QRCodeEffectPage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    double width = MediaQuery.of(context).size.width / 3 * 2;
    return Center(
      child: Container(
        child: QRCodeLayout(
          // 设置扫描框的大小
          size: Size(width, width),
          // 直角长度
          angleLength: 0,
          // 直角线粗细
          angleWidth: 4,
          // 边框宽度
          borderWidth: 0.5,
          // 是否显示边框
          showBorder: false,
          // 扫描动画时长
          animationDuration: 1500,
          // 扫描横线宽度
          scannerWidth: 1,
        ),
        color: Colors.transparent,
      ),
    );
  }
}

class QRCodeLayout extends StatefulWidget {
  /// 扫描框的大小
  final Size size;

  /// 直角长度
  final double angleLength;

  /// 直角宽度
  final double angleWidth;

  /// 边框宽度
  final double borderWidth;

  /// 是否显示边框
  final bool showBorder;

  /// 扫描横线宽度
  final double scannerWidth;

  /// 动画时长，默认 1500 毫秒
  final int animationDuration;

  const QRCodeLayout({
    Key key,
    @required this.size,
    this.angleWidth,
    this.angleLength,
    this.borderWidth,
    this.showBorder,
    this.scannerWidth,
    this.animationDuration,
  }) : super(key: key);

  @override
  _QRCodeLayoutState createState() => _QRCodeLayoutState();
}

class _QRCodeLayoutState extends State<QRCodeLayout>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;
  Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
        duration: Duration(milliseconds: widget.animationDuration ?? 1500),
        vsync: this);
    _animation =
        Tween(begin: 0.0, end: widget.size.height).animate(_controller);
    _controller.repeat();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedQRCodeScanner(
      size: widget.size,
      animation: _animation,
      angleLength: widget.angleLength,
      angleWidth: widget.angleWidth,
      borderWidth: widget.borderWidth,
      showBorder: widget.showBorder,
      scannerWidth: widget.scannerWidth,
    );
  }
}

class AnimatedQRCodeScanner extends AnimatedWidget {
  final Size size;

  /// 直角长度
  final double angleLength;

  /// 直角宽度
  final double angleWidth;

  /// 边框宽度
  final double borderWidth;

  /// 是否显示边框
  final bool showBorder;

  /// 扫描横线宽度
  final double scannerWidth;

  AnimatedQRCodeScanner({
    @required Animation<double> animation,
    @required this.size,
    this.angleLength,
    this.angleWidth,
    this.borderWidth,
    this.showBorder,
    this.scannerWidth,
  }) : super(listenable: animation);

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      size: size,
      painter: QRCodeBorderPainter(
        angleLength: angleLength,
        angleWidth: angleWidth,
        borderWidth: borderWidth,
        showBorder: showBorder,
      ),
      foregroundPainter: QRCodeScannerPainter(
        (listenable as Animation).value,
        scannerWidth,
      ),
    );
  }
}

/// 二维码边框
class QRCodeBorderPainter extends CustomPainter {
  Paint _paint = Paint()
    ..color = Colors.green
    ..style = PaintingStyle.stroke
    ..strokeCap = StrokeCap.round
    ..isAntiAlias = true;

  /// 直角长度
  double angleLength;

  /// 直角宽度
  double angleWidth;

  /// 边框宽度
  double borderWidth;

  /// 是否显示边框
  bool showBorder;

  QRCodeBorderPainter({
    this.angleLength,
    this.angleWidth,
    this.borderWidth,
    this.showBorder,
  });

  @override
  void paint(Canvas canvas, Size size) {
    _paint.strokeWidth = angleWidth ?? 5;
    double length = angleLength ?? 30;
    // 左上角
    canvas.drawLine(Offset(0, 0), Offset(0, length), _paint);
    canvas.drawLine(Offset(0, 0), Offset(length, 0), _paint);
    // 右上角
    canvas.drawLine(
        Offset(size.width - length, 0), Offset(size.width, 0), _paint);
    canvas.drawLine(Offset(size.width, 0), Offset(size.width, length), _paint);
    // 左下角
    canvas.drawLine(
        Offset(0, size.height), Offset(0, size.height - length), _paint);
    canvas.drawLine(
        Offset(0, size.height), Offset(length, size.height), _paint);
    // 右下角
    canvas.drawLine(Offset(size.width, size.height),
        Offset(size.width, size.height - length), _paint);
    canvas.drawLine(Offset(size.width, size.height),
        Offset(size.width - length, size.height), _paint);
    // 绘制边框
    if (showBorder ?? true) {
      _paint.strokeWidth = borderWidth ?? 1;
      // 左
      canvas.drawLine(
          Offset(0, length), Offset(0, size.height - length), _paint);
      // 上
      canvas.drawLine(
          Offset(length, 0), Offset(size.width - length, 0), _paint);
      // 右
      canvas.drawLine(Offset(size.width, length),
          Offset(size.width, size.height - length), _paint);
      // 下
      canvas.drawLine(Offset(length, size.height),
          Offset(size.width - length, size.height), _paint);
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false;
  }
}

/// 中间扫描器
class QRCodeScannerPainter extends CustomPainter {
  Paint _paint = Paint()..color = Colors.green;

  final double yOffset;
  final double lineWidth;

  QRCodeScannerPainter(this.yOffset, this.lineWidth) : assert(yOffset >= 0);

  @override
  void paint(Canvas canvas, Size size) {
    _paint..strokeWidth = (lineWidth ?? 2);
    canvas.drawLine(Offset(0, yOffset), Offset(size.width, yOffset), _paint);
    // Path path = Path();
    // path.moveTo(0, 100);
    // path.conicTo(size.width / 2, 50, size.width, 100, 1);
    // canvas.drawPath(path, _paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}
